package mn.idax.exchange.otc.service.impl;

import com.alibaba.fastjson.JSON;
import mn.idax.exchange.common.CalendarUtil;
import mn.idax.exchange.otc.domain.OrderChangeDTO;
import mn.idax.exchange.otc.domain.capital.CapitalAPIParamsDTO;
import mn.idax.exchange.otc.entity.*;
import mn.idax.exchange.otc.enums.*;
import mn.idax.exchange.otc.enums.response.OrderResponseEnum;
import mn.idax.exchange.otc.exception.IdaxException;
import mn.idax.exchange.otc.mapper.AccountMapper;
import mn.idax.exchange.otc.mapper.OTCOrderMapper;
import mn.idax.exchange.otc.service.*;
import mn.idax.exchange.otc.util.DateUtils;
import mn.idax.exchange.otc.util.LogUtils;
import mn.idax.exchange.otc.util.MessageNotifyUtils;
import mn.idax.exchange.otc.vo.AccountFeeDTO;
import mn.idax.exchange.otc.vo.AccountFrozenDTO;
import mn.idax.exchange.otc.vo.AccountUnFrozenDTO;
import mn.idax.exchange.otc.vo.UpdateOrderCancelTime;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Create by jack_huang on 2018/8/15
 */
@Service
public class AccountServiceImpl implements AccountService {


    private static ExecutorService executorService = Executors.newFixedThreadPool(2);

    @Autowired
    private AccountMapper accountMapper;

    @Autowired
    private OTCOrderService otcOrderService;

    @Autowired
    private AccountTransactionService transactionService;

    @Autowired
    private OTCOrderMapper otcOrderMapper;

    @Autowired
    private OTCSysDictionaryItemService otcSysDictionaryItemService;

    @Autowired
    private OTCAdsService otcAdsService;

    @Autowired
    private OTCOrderLogService orderLogService;

    @Autowired
    private AccountCapitalService accountCapitalService;

    @Autowired
    private JiGuangService jiGuangService;

    @Autowired
    private CapitalAPIBaseService capitalAPIBaseService;


    @Override
    public Account getAccountByCoinIdAndUserId(Integer userId, Integer coinid) {


        return accountCapitalService.getBalances(userId, coinid);
    }

    @Override
    public int forzenUserAccount(AccountFeeDTO accountFeeDTO) {
        LogUtils.info(this.getClass(), "预期冻结用户账户资金条数一条");

        Integer coinId = accountFeeDTO.getPairid();
        Integer userId = accountFeeDTO.getUserId();
        Account account = getAccountByCoinIdAndUserId(userId, coinId);
        if (account == null) {
            LogUtils.warn(this.getClass(), "forzenUserAccountFail,forzenUserAccount: " + JSON.toJSONString(accountFeeDTO));
            throw new IdaxException(OrderResponseEnum.ACCOUNT_ISNOTEXOST.getRespCode());
        }

        AccountFrozenDTO accountFrozenDTO = new AccountFrozenDTO(userId, accountFeeDTO.getQty(), coinId, account.getRowversion(),
                DateUtils.getCalendarToDate(CalendarUtil.getUTCDate(new Date())));

        int updateFlag = accountMapper.forzenUserAccount(accountFrozenDTO);

        LogUtils.info(this.getClass(), "成功冻结用户账户资金条数:" + updateFlag);

        return updateFlag;
    }

    //通用解冻用户部分资金 只解冻出币的一方==卖方
    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = {RuntimeException.class, Exception.class})
    public int unforzenUserAccountCommon(OTCOrder otcOrder) {
        LogUtils.info(this.getClass(), "预期解冻用户账户资金条数一条");
//        for (OTCOrder otcOrder : otcOrders) {
        Integer coinId = otcOrder.getCoinid();
        Integer userId = otcOrder.getSeller();

        //解冻之前先查询account

        Account account = getAccountByCoinIdAndUserId(userId, coinId);

        if (account == null) {
            LogUtils.warn(this.getClass(), "unforzenUserAccountCommonFail,userid: " + userId + " coinId:" + coinId);
            throw new IdaxException(OrderResponseEnum.ACCOUNT_ISNOTEXOST.getRespCode());
        }

        int updateFlag = accountMapper.unforzenUserAccount(
                new AccountUnFrozenDTO(userId, otcOrder.getQty(), coinId, account.getRowversion(),
                        DateUtils.getCalendarToDate(CalendarUtil.getUTCDate(new Date()))));


        LogUtils.info(this.getClass(), "成功解冻用户账户资金条数:" + updateFlag);
//        LogUtils.info(this.getClass(), "解冻用户账户资金执行结束:********");
        return updateFlag;
    }

    /**
     * 没用
     * 解冻用户部分资金 + 更改订单状态为取消 + 解冻手续费
     * @param otcOrders
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = {RuntimeException.class, Exception.class})
    public void unforzenUserAccountPayTimeOut(List<OTCOrder> otcOrders) {
        LogUtils.info(this.getClass(), "预期解冻用户账户资金条数:" + otcOrders.size());
        LogUtils.info(this.getClass(), "预期解冻手续费条数:" + otcOrders.size());

        List<Integer> listOrderPlacer = new ArrayList<>();
        for (OTCOrder otcOrder : otcOrders) {
            Integer coinId = otcOrder.getCoinid();
            Integer seller = otcOrder.getSeller();

            OTCOrder baseOrder = this.otcOrderService.getOrderByOrderNum(otcOrder.getNumber());
            if (baseOrder.getOrderstate().equals(OrderState.STATE_EXPIRE)) {
                continue;
            }
            //更新订单状态为过期
            if (!otcOrderService.updateOrderStateCommon(otcOrder,new OrderChangeDTO(
                    otcOrder.getNumber(), OrderState.STATE_EXPIRE.getCode()), otcOrder.getBuyer())) {
                throw new IdaxException(OrderResponseEnum.TIMEOUTORDER_UNFROZEN_FAIL.getRespCode());
            }
            OTCAds otcAds = this.otcAdsService.selectByPrimaryKey(otcOrder.getAdid());

            //解冻之前先查询account
            int serviceCharge = 0;
            Account account = getAccountByCoinIdAndUserId(seller, coinId);

            if (account == null) {
                LogUtils.warn(this.getClass(), "unforzenUserAccountPayTimeOut,userid: " + seller + " coinId:" + coinId);
                throw new IdaxException(OrderResponseEnum.ACCOUNT_ISNOTEXOST.getRespCode());
            }


            int updateFlag = accountMapper.unforzenUserAccount(
                    new AccountUnFrozenDTO(seller, otcOrder.getQty(), coinId, account.getRowversion(),
                            DateUtils.getUTCNow()));

            if (updateFlag <= 0) {
                throw new IdaxException(OrderResponseEnum.ORDERE_UNFROZENACCOUNTFAIL.getRespCode());
            }

            // 添加取消时间
            UpdateOrderCancelTime updateOrderCancelTime = new UpdateOrderCancelTime(DateUtils.getUTCNow(), otcOrder.getNumber());
            if (otcOrderMapper.updateOrderCancelTime(updateOrderCancelTime) < 1) {
                LogUtils.warn(this.getClass(), "updateOrderCancelTimeFail,req: " + JSON.toJSONString(updateOrderCancelTime));
                throw new IdaxException(OrderResponseEnum.UPDATE_ORDERSTATEFAIL.getRespCode());
            }

            // 解冻手续费
            AccountFeeDTO accountFeeDTO = new AccountFeeDTO(otcAds.getPublisherid(), otcOrder.getQty(), otcOrder.getCoinid());
            serviceCharge = this.unforzenServiceCharge(accountFeeDTO);

            if (serviceCharge != 1) {
                LogUtils.warn(this.getClass(), "unforzenServiceChargeFail,req: " + JSON.toJSONString(accountFeeDTO));
                throw new IdaxException(OrderResponseEnum.UNFORZEN_ACCOUNTFAIL.getRespCode());
            }

            //insert account transaction
            if (!transactionService.insertAccountTransactionCancelOrder(AccountTransactionEnum.TRANSACTIONTYPE_CANCELORDER.getCode(), baseOrder)) {
                throw new IdaxException(OrderResponseEnum.ACCOUNTTRANSACTION_FAIL.getRespCode());
            }

            //更新 orderlog
            if (this.orderLogService.insertOtcOrderLog(otcOrder, OrderState.STATE_BUYER_CANCEL.getCode()) <= 0) {
                throw new IdaxException(OrderResponseEnum.ORDERLOG_INSERTFAIL.getRespCode());
            }

            //发送消息
            otcOrderService.pushOrderStateMessage(otcOrder, OrderState.STATE_EXPIRE.getCode(),otcOrder.getBuyer());

            // 获取下单人(非广告发布者即为下单人)
            Integer orderPlacer = otcAds.getPublisherid().equals(otcOrder.getBuyer()) ? otcOrder.getSeller() : otcOrder.getBuyer();
            listOrderPlacer.add(orderPlacer);

            // 7.发送我的通知消息
            MessageNotifyUtils.sendNotify(otcOrder.getBuyer(),
                    otcOrder.getUniquekey(),
                    MessageNotifyEnum.BodyTemplate.order_expire);
        }
        // 添加取消次数，放到外层主要防止事务回滚问题 add by dison 2018/09/03
        listOrderPlacer.forEach(orderPlacer -> otcOrderService.addCancelOrderNum(orderPlacer));
        LogUtils.info(this.getClass(), "解冻用户账户资金条数" + otcOrders.size());
        LogUtils.info(this.getClass(), "解冻手续费条数" + otcOrders.size());
        LogUtils.info(this.getClass(), "超时扫描执行结束");

    }

    //订单完成时调用 转账-买家币进买家账户
    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = {RuntimeException.class, Exception.class})
    public Boolean transferAccounts(String orderNum) {

        LogUtils.info(this.getClass(), "订单完成********开始转账********");

        OTCOrder order = otcOrderService.getOrderByOrderNum(orderNum);
        if (order == null) {
            throw new IdaxException(OrderResponseEnum.ORDER_ISNOTEXIST.getRespCode());
        }
        Integer buyer = order.getBuyer();
        Integer seller = order.getSeller();
        Integer coinid = order.getCoinid();

        Account buyerAccount = this.insertAccountCoinByVerifyAccount(buyer, coinid);
        Account sellerAccount = this.insertAccountCoinByVerifyAccount(seller, coinid);

        int transferBuyer = accountMapper.transferToBuyer(new AccountUnFrozenDTO(buyer, order.getQty(), coinid, buyerAccount.getRowversion(), DateUtils.getCalendarToDate(CalendarUtil.getUTCDate(new Date()))));
        int transferSeller = accountMapper.transferBySeller(new AccountUnFrozenDTO(seller, order.getQty(), coinid, sellerAccount.getRowversion(),
                DateUtils.getCalendarToDate(CalendarUtil.getUTCDate(new Date()))));

        if (transferBuyer <= 0 || transferSeller <= 0) {
            throw new IdaxException(OrderResponseEnum.ACCOUNT_TRANSFERFAIL.getRespCode());
        }

        LogUtils.info(this.getClass(), "转账完成********");
        return true;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_UNCOMMITTED, rollbackFor = {RuntimeException.class, Exception.class})
    public Boolean transferAccountsByOrder(OTCOrder order) {

        LogUtils.info(this.getClass(), "订单完成********开始转账********");

        if (order == null) {
            throw new IdaxException(OrderResponseEnum.ORDER_ISNOTEXIST.getRespCode());
        }
        Integer buyer = order.getBuyer();
        Integer seller = order.getSeller();
        Integer coinid = order.getCoinid();

        Account buyerAccount = this.insertAccountCoinByVerifyAccount(buyer, coinid);
        Account sellerAccount = this.insertAccountCoinByVerifyAccount(seller, coinid);

        int transferBuyer = accountMapper.transferToBuyer(new AccountUnFrozenDTO(buyer, order.getQty(), coinid, buyerAccount.getRowversion(), DateUtils.getCalendarToDate(CalendarUtil.getUTCDate(new Date()))));
        int transferSeller = accountMapper.transferBySeller(new AccountUnFrozenDTO(seller, order.getQty(), coinid, sellerAccount.getRowversion(),
                DateUtils.getCalendarToDate(CalendarUtil.getUTCDate(new Date()))));

        if (transferBuyer <= 0 || transferSeller <= 0) {
            throw new IdaxException(OrderResponseEnum.ACCOUNT_TRANSFERFAIL.getRespCode());
        }

        LogUtils.info(this.getClass(), "转账完成********");
        return true;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_UNCOMMITTED, rollbackFor = {RuntimeException.class, Exception.class})
    public Account insertAccountCoinByVerifyAccount(Integer userId, Integer coinId) {
        AccountExample accountExample = new AccountExample();
        accountExample.createCriteria()
                .andUseridEqualTo(userId)
                .andCoinidEqualTo(coinId);

        List<Account> listAccount = this.accountMapper.selectByExample(accountExample);
        // 1.判断账户是否有对应币种信息资料
        if (listAccount.isEmpty()) {
            // 2.不存在资料需要初始化账户币种信息
            Account account = new Account();
            account.setUserid(userId);
            account.setCoinid(coinId);
            account.setAvailableamount(BigDecimal.ZERO);
            account.setFrozenamount(BigDecimal.ZERO);
            account.setCreatetime(DateUtils.getUTCNow());
            account.setUpdatetime(DateUtils.getUTCNow());

            this.accountMapper.insertSelective(account);
            return account;
        }

        return listAccount.get(0);
    }

    @Override
    public boolean unforzenUserAccountPayTimeOut(OTCOrder otcOrder, CapitalAPIParamsDTO capitalAPIParamsDTO) {

        LogUtils.info(this.getClass(),"unforzenUserAccountPayTimeOut start,order detail " + JSON.toJSONString(otcOrder) + "unForzenOrderCapitalAPIParamsDTO data is " + JSON.toJSONString(capitalAPIParamsDTO));
        Boolean success = capitalAPIBaseService.excludeCreateOrderRequestAPI(capitalAPIParamsDTO);

        if (success) {
            //发送消息
            otcOrderService.pushOrderStateMessage(otcOrder, OrderState.STATE_EXPIRE.getCode(), otcOrder.getBuyer());

            // 7.发送我的通知消息
            MessageNotifyUtils.sendNotify(otcOrder.getBuyer(),
                    otcOrder.getNumber(),
                    MessageNotifyEnum.BodyTemplate.order_expire);

            // 获取下单人(非广告发布者即为下单人)
            otcOrderService.addCancelOrderNum(otcOrder.getBuyer());
            //取消次数过3 需要冻结用户
            otcOrderService.cancelOrderNumMoreThanThree(otcOrder.getBuyer());

            OTCThreadPool.THREAD_POOL.execute(() -> {
                try {
                    //极光推送给买方和卖方  您有一笔交易订单因为逾时未处理，由系统取消交易
                    jiGuangService.pushToBuyerAndSeller(otcOrder, JiGuangPushEnum.ORDER_EXPIRE);

                } catch (Exception e) {
                    LogUtils.warn(this.getClass(),"pushToBuyerAndSellerAtUnfrozenOrder fail," + ExceptionUtils.getStackTrace(e));
                }

            });

        }
        return true;
    }

    //冻结手续费
    @Override
    public int forzenServiceCharge(AccountFeeDTO accountFeeDTO) {

        LogUtils.info(this.getClass(), "预期冻结用户账户手续费条数一条********");

        Integer userId = accountFeeDTO.getUserId();
        Integer coinId = accountFeeDTO.getPairid();
        Account account = getAccountByCoinIdAndUserId(userId, coinId);
        if (account == null) {
            throw new IdaxException(OrderResponseEnum.ACCOUNT_ISNOTEXOST.getRespCode());
        }

        List<OTCSysDictionaryItem> otcSysDictionaryItems = otcSysDictionaryItemService.listItem(DictEnum.COIN_FEE_RATE.getCode());
        if (otcSysDictionaryItems.isEmpty()) {
            throw new IdaxException(OrderResponseEnum.COIN_FEE_RATE_ISEMPTY.getRespCode());
        }

        //手续费费率
        BigDecimal coinFeeRate = new BigDecimal(otcSysDictionaryItems.get(0).getItemdesc());

        int updateFlag = accountMapper.forzenUserAccount(new AccountFrozenDTO(userId, accountFeeDTO.getQty().multiply(coinFeeRate).setScale(8,BigDecimal.ROUND_DOWN),
                coinId, account.getRowversion(), DateUtils.getCalendarToDate(CalendarUtil.getUTCDate(new Date()))));

        LogUtils.info(this.getClass(), "冻结用户账户手续费条数********" + updateFlag);
        LogUtils.info(this.getClass(), "冻结用户账户手续费执行结束:********");

        return updateFlag;
    }

    //解冻手续费
    @Override
    public int unforzenServiceCharge(AccountFeeDTO accountFeeDTO) {
        LogUtils.info(this.getClass(), "预期解冻用户账户手续费条数一条********");
        //冻结之前先查出账户

        Integer userId = accountFeeDTO.getUserId();
        Integer coinId = accountFeeDTO.getPairid();
        Account account = getAccountByCoinIdAndUserId(userId, coinId);
        if (account == null) {
            throw new IdaxException(OrderResponseEnum.ACCOUNT_ISNOTEXOST.getRespCode());
        }
        List<OTCSysDictionaryItem> otcSysDictionaryItems = otcSysDictionaryItemService.listItem(DictEnum.COIN_FEE_RATE.getCode());
        if (otcSysDictionaryItems.isEmpty()) {
            throw new IdaxException(OrderResponseEnum.COIN_FEE_RATE_ISEMPTY.getRespCode());
        }

        //手续费费率
        BigDecimal coinFeeRate = new BigDecimal(otcSysDictionaryItems.get(0).getItemdesc());

        int updateFlag = accountMapper.unforzenUserAccount(new AccountUnFrozenDTO(userId, accountFeeDTO.getQty().multiply(coinFeeRate).setScale(8,BigDecimal.ROUND_DOWN),
                coinId, account.getRowversion(), DateUtils.getCalendarToDate(CalendarUtil.getUTCDate(new Date()))));


        LogUtils.info(this.getClass(), "解冻用户账户手续费条数********" + updateFlag);
        LogUtils.info(this.getClass(), "解冻用户账户手续费执行结束:********");
        return updateFlag;
    }

    //收取广告主手续费
    @Override
    public int getServiceCharge(AccountFeeDTO accountFeeDTO) {

        LogUtils.info(this.getClass(), "预期收取广告主手续费条数一条********");

        Integer userId = accountFeeDTO.getUserId();
        Integer coinId = accountFeeDTO.getPairid();
        Account account = getAccountByCoinIdAndUserId(userId, coinId);
        if (account == null) {
            throw new IdaxException(OrderResponseEnum.ACCOUNT_ISNOTEXOST.getRespCode());
        }

        List<OTCSysDictionaryItem> otcSysDictionaryItems = otcSysDictionaryItemService.listItem(DictEnum.COIN_FEE_RATE.getCode());
        if (otcSysDictionaryItems.isEmpty()) {
            throw new IdaxException(OrderResponseEnum.COIN_FEE_RATE_ISEMPTY.getRespCode());
        }

        //手续费费率
        BigDecimal coinFeeRate = new BigDecimal(otcSysDictionaryItems.get(0).getItemdesc());

        int updateFlag = accountMapper.getServiceCharge(new AccountFrozenDTO(userId, accountFeeDTO.getQty().multiply(coinFeeRate).setScale(8,BigDecimal.ROUND_DOWN),
                coinId, account.getRowversion(), DateUtils.getCalendarToDate(CalendarUtil.getUTCDate(new Date()))));

        LogUtils.info(this.getClass(), "收取广告主手续费条数********" + updateFlag);
        LogUtils.info(this.getClass(), "收取广告主手续费执行结束********");
        return updateFlag;
    }

}
